package org.mentalsmash.crossroads;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.log4j.Logger;
import org.mentalsmash.crossroads.Crossword.CrosswordEntry;

public class SVGExporter implements CrosswordExporter<String> {

	private final static Logger log = Logger.getLogger(SVGExporter.class);
	
    protected final static int CELL_HEIGHT=50;
    protected final static int CELL_WIDTH=50;
    private final static int START_X = 50;
    private final static int START_Y = 50;
    private final static int DX = 16;
    private final static int DY = 36;
    private final static int SMALL_DX = 3;
    private final static int SMALL_DY = 11;
    private final static int CHAR_FONT_SIZE = 26;
    private final static int NUMBER_FONT_SIZE = 12;
    private final static int DEF_TRANSLATION_FACTOR  = 2;
    private final static int DEF_ROW_HEIGHT = 20;
    private final static int DEF_FONT_SIZE = 16;
    
    private final static String FONT_FACE = "arial, sans-serif";
    
    private final static String BACKGROUND_COLOR = "white";
    private final static String BLANK_COLOR = "white";
    private final static String BLACK = "black";

    
    private boolean _withDefinitions = true;
    private boolean _withHeading = true;

    
    
    /** Set whether the exported svg will show the crossword definitions
     * 
     * @param withDefinitions true if you want definitions embedded in the svg
     */
	public void setWithDefinitions(boolean withDefinitions) {
		_withDefinitions = withDefinitions;
	}

	/**
	 * Check if the exported svg will embed definitions
	 * @return true if it will
	 */
	public boolean isWithDefinitions() {
		return _withDefinitions;
	}



	public void setWithHeading(boolean withHeading) {
		_withHeading = withHeading;
	}

	public boolean isWithHeading() {
		return _withHeading;
	}

	/**
	 * Creates a new SVGExporter. It exports a Crossword instance into an svg representation. By default it includes
	 * definitions in the exported svg
	 */
	public SVGExporter(){
		
	}
	
	/**
	 * Creates a new SVGExporter. It exports a Crossword instance into an svg representation.
	 *
	 * @param withDefinitions false if you do not want crossword definitions to be embedded in the svg picture
	 */
	public SVGExporter(boolean withDefinitions, boolean withHeading){
		setWithDefinitions(withDefinitions);
		setWithHeading(withHeading);

	}
	
	
	@Override
	public String toExternalRepresentation(Crossword input) {
		ByteArrayOutputStream outputString = new ByteArrayOutputStream();
		buildSvgDocument(input,outputString);
		String built = null;
		try{
			built = new String(outputString.toByteArray(),Configuration.getInstance().getEncoding());
		} catch (UnsupportedEncodingException e ){
			log.fatal("Unsupported encoding while writing svg: " + Configuration.getInstance().getEncoding());
			e.printStackTrace();
			System.exit(-1);
		} finally {
			try{ 
			 outputString.close();
			} catch (IOException e) {
				log.warn("While closing SVG outputstream: "+ e.getMessage()); 
			}
		}
		
		return built;
		
	}
	
	/**
	 * Creates an SVG document representing the crossword passed as input and writes it to the given OutputStream.
	 * Note that the outputstream must be opened and will not be closed after this method returns
	 * @param input the crossword to build the representation of
	 * @param out an outputStream where to write the svg document
	 */
	public void buildSvgDocument(Crossword input,OutputStream out){

		log.debug("Generating svg representation...");
		PrintStream outSvg = new PrintStream(out);

		int totWidth = (input.getNumCols()+2)* CELL_WIDTH;
		int totHeight = (input.getNumRows() + 2) * CELL_HEIGHT;
		
		if(isWithDefinitions())
			totHeight += (DEF_TRANSLATION_FACTOR -1)* CELL_HEIGHT +(input.getOrderedAcrossEntries().size()+input.getOrderedDownEntries().size()+2)*DEF_ROW_HEIGHT;
		
		if(isWithHeading()){
			outSvg.append("<?xml version='1.0' encoding='UTF-8'?>\n");
			outSvg.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n\t"+
					"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n");
		}
		outSvg.append("<svg " +
				"xmlns='http://www.w3.org/2000/svg' " +
				"width='"+totWidth +"' height='"+totHeight+"'>\n");
		//draw the bounding box only if definitions are included
		if(isWithDefinitions())
			outSvg.append("<rect width='"+ totWidth + "' height='"+ totHeight + "' fill='"+ BACKGROUND_COLOR +"' />\n");
		outSvg.append("<title> Crossword " + input.getCrosswordId() + " </title>");
		drawCrosswordGroup(input, outSvg);
		if(isWithDefinitions())
			drawDefinitionsGroup(input,outSvg);
		outSvg.append("</svg>\n");
		log.debug("Done.");
		
	}

	private void drawDefinitionsGroup(Crossword input, PrintStream outSvg) {
		
		List<CrosswordEntry> across = input.getOrderedAcrossEntries();
		List<CrosswordEntry> down = input.getOrderedDownEntries();
		
		outSvg.append("<g id='definitions' transform='translate("+ START_X +"," + CELL_HEIGHT*(input.getNumRows()+DEF_TRANSLATION_FACTOR) + ")' >\n");
		outSvg.append("<g id='across'>\n");
		outSvg.append("<text font-size='" + DEF_FONT_SIZE +"' font-weight='bold' font-face='"+FONT_FACE+"'>Across: </text>\n");
		drawDefinitions(across,outSvg);
		outSvg.append("</g>\n");
		
		outSvg.append("<g id='down' transform='translate(0," + DEF_ROW_HEIGHT * (across.size()+2) + ")' >\n");
		outSvg.append("<text font-size='" + DEF_FONT_SIZE +"' font-weight='bold' font-face='"+FONT_FACE+"'>Down: </text>\n");
		drawDefinitions(down,outSvg);
		outSvg.append("</g>\n");
		outSvg.append("</g>\n");
		
	}



	private void drawDefinitions(List<CrosswordEntry> across,
			PrintStream outSvg) {
		for(int row=0;row<across.size();row++){
			outSvg.append("<text x='0' y='" + (row+1)*DEF_ROW_HEIGHT +"' font-size='" + DEF_FONT_SIZE +"' font-face='"+FONT_FACE+"' >\n");
			outSvg.append(across.get(row).getFriendlyNumber()+". " + across.get(row).getDefinition());
			outSvg.append("</text>\n");
		}
		
	}

	private void drawCrosswordGroup(Crossword input, PrintStream outSvg) {
		
		outSvg.append("<g id='crossword'>\n");
		for(int i = 0 ; i< input.getNumRows(); i++){
			drawCrosswordRow(input,i,outSvg);
		}
		outSvg.append("</g>\n");
		
	}

	private void drawCrosswordRow(Crossword input, int curRow, PrintStream outSvg) {
		outSvg.append("<g id='row"+ curRow + "' transform='translate(0," + CELL_HEIGHT*curRow + ")' >\n");
		for(int curCol=0; curCol< input.getNumCols();curCol++)
		{
			String color = null;
			if(input.getCharAt(curRow, curCol) == Crossword.BLACK_CELL_CHAR)
				color = BLACK;
			else color = BLANK_COLOR;
			
			outSvg.append( "<rect x='" + (START_X+(CELL_WIDTH*curCol)) +  
					"' y='" + (START_Y) + 
					"' width='" + CELL_WIDTH + 
					"' height='" + CELL_HEIGHT +
					"' fill='" + color + 
					"' stroke='black' stroke-width='1' />\n");
			
			outSvg.append("<text x ='"+ (START_X+(CELL_WIDTH*curCol)) +
					"' y = '" + CELL_WIDTH +
					"' dx='" + DX + 
					"'  dy='" + DY + 
					"' font-size='" + CHAR_FONT_SIZE + 
					"' font-weight='bold' font-face='"+FONT_FACE+"'" +
					" fill='" + BLACK + 
					"'>");
			outSvg.append(input.getCharAt(curRow, curCol));
			outSvg.append("</text>\n");
			
			CrosswordEntry curEntry = null;
			curEntry = input.getEntry(curRow + "_" + curCol + "_a");
			if(curEntry == null)
				curEntry = input.getEntry(curRow + "_" + curCol + "_d");
			if(curEntry != null){
				outSvg.append("<text x ='"+ (START_X+(CELL_WIDTH*curCol)) +
						"' y = '" + CELL_WIDTH +
						"' dx='" + SMALL_DX + 
						"'  dy='" + SMALL_DY + 
						"' font-size='" + NUMBER_FONT_SIZE + 
						"' font-weight='bold' font-face='"+FONT_FACE+"'" +
						" fill='" + BLACK + 
						"'>");
				outSvg.append(curEntry.getFriendlyNumber() + "");
				outSvg.append("</text>\n");
			}
			
				
				
		}
		outSvg.append("</g>\n");

	}


	

}
